Simple Authentication for Docker Services
When building services with Docker, securing access to them is often a crucial step. Whether you want to protect an internal API or limit access to a simple web service, you can achieve this with a lightweight authentication layer. In this post, I’ll show you two approaches to add a simple login step to your Docker services.
Two Approaches to Authentication:
- Nginx Basic Authentication: Use Nginx as a reverse proxy to protect your services with HTTP basic authentication.
- Custom Backend Authentication: Implement a custom login system in your backend using simple token-based authentication.
By the end, you’ll have a fully functional example where users must log in before accessing your services.
Approach 1: Nginx Basic Authentication
This method uses Nginx as a reverse proxy in front of your services, where it asks users for a username and password before allowing access.
Step 1: Create the Docker Compose Setup
We will run an Nginx container alongside your other services. Nginx will handle the authentication and forward traffic to your backend or frontend service.
version: '3'
services:
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf # Custom Nginx configuration
- ./auth:/etc/nginx/.htpasswd # .htpasswd file for authentication
ports:
- "80:80" # Expose Nginx on port 80
restart: always # Restart the service if it fails
backend:
image: node:14 # Example backend using Node.js
volumes:
- ./backend:/usr/src/app # Mount the backend directory
ports:
- "8080:8080" # Expose the backend on port 8080
command: "node server.js" # Command to start the backend
restart: always
This setup assumes you have a backend
service that you want to protect. The Nginx container will handle authentication before proxying requests to it.
Step 2: Configure Nginx for Basic Authentication
In your nginx.conf
, you’ll need to enable basic authentication and set up Nginx to forward requests to your backend.
server {
listen 80;
location / {
# Enable basic authentication
auth_basic "Protected Area";
auth_basic_user_file /etc/nginx/.htpasswd;
# Proxy requests to the backend service
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Step 3: Create the .htpasswd
File
Solution1:
To create a username and password for basic authentication, you’ll use the htpasswd
tool. This file will store the credentials in an encrypted format.
docker run --rm --entrypoint htpasswd httpd:2 -c ./auth/.htpasswd user1
This command will prompt you for a password for user1
and create the .htpasswd
file in the ./auth/
directory.
Solution2:
docker run --rm httpd:alpine htpasswd -nbB username password
Step 4: Start the Services
Now, you can start your services using Docker Compose:
docker-compose up -d
When users visit http://localhost
, they’ll be prompted for a username and password. Only after successful authentication will they be forwarded to the backend.
Approach 2: Custom Backend Authentication
In some cases, you may want more control over the authentication flow. Instead of relying on Nginx, you can implement a simple token-based login system in your backend.
Step 1: Docker Compose Setup
We’ll use a backend service written in Node.js to handle the login process and return a token upon successful authentication.
version: '3'
services:
backend:
image: node:14
volumes:
- ./backend:/usr/src/app
ports:
- "8080:8080"
command: "node server.js"
restart: always
This setup only includes the backend, but you can add other services (such as a frontend) and secure them in a similar way.
Step 2: Backend Code with Token Authentication
Here’s a simple example of a Node.js server (server.js
) that implements token-based authentication. When the user logs in, a token is generated and sent back to the client. This token must be included in subsequent requests to access protected routes.
server.js
(Node.js Backend):
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const jwt = require('jsonwebtoken');
const SECRET_KEY = 'your-secret-key';
app.use(bodyParser.json());
let users = [
{ username: 'user1', password: 'password123' }
];
// Login route
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '1h' });
return res.json({ token });
});
// Protected route
app.get('/protected', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: 'No token provided' });
}
const token = authHeader.split(' ')[1];
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) {
return res.status(403).json({ message: 'Invalid token' });
}
return res.json({ message: 'Welcome to the protected route!', user });
});
});
app.listen(8080, () => {
console.log('Backend running on port 8080');
});
Step 3: Interacting with the Backend
To interact with this backend:
- First, the client sends a POST request to
/login
with the username and password. - If the login is successful, the server returns a token.
- The client must include this token in the
Authorization
header (asBearer <token>
) for subsequent requests to access protected routes.
Example Login Request:
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"username": "user1", "password": "password123"}'
This returns a JSON response with the token:
{
"token": "your.jwt.token"
}
Accessing a Protected Route:
Once logged in, you can access protected routes by passing the token in the Authorization
header:
curl -X GET http://localhost:8080/protected \
-H "Authorization: Bearer your.jwt.token"
If the token is valid, you will receive access to the route.
Conclusion
Both approaches offer simple ways to add authentication to your Docker services.
- Nginx Basic Authentication is a fast and easy solution that doesn’t require changes to your backend but has limited flexibility.
- Custom Backend Authentication provides more control and can be extended with additional security features, such as session management or role-based access control.
You can choose the solution that best fits your project’s needs. For quick protection of internal services, Nginx basic authentication works well. For more complex applications, a custom backend authentication system is often a better choice.
Feel free to try both approaches and let me know how it works for your project!